package com.klasster.recruitmentPlatform.common.recommendation;

import cn.hutool.core.lang.Pair;
import cn.hutool.core.lang.hash.Hash;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.klasster.recruitmentPlatform.common.recommendation.data.SeekerVector;
import com.klasster.recruitmentPlatform.domain.po.JobIntention;
import com.klasster.recruitmentPlatform.domain.po.Seeker;
import com.klasster.recruitmentPlatform.service.impl.JobIntentionServiceImpl;
import com.klasster.recruitmentPlatform.service.impl.SeekerServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.time.Period;
import java.util.*;

@RequiredArgsConstructor
@Component
public class KPrototypesCluster {

    private final SeekerServiceImpl seekerService;
    private final JobIntentionServiceImpl jobIntentionService;

    //属性权重
    private final double w_age = 0.20;
    private final double w_gender = 0.12;
    private final double w_identity = 0.20;
    private final double w_job_code = 2.0;
    private final double w_job_address = 0.1;
    private final double w_salary_min = 0.12;
    private final double w_salary_max = 0.12;

    /**
     * 获取求职者所在聚类的所有人员
     * @param seeker_id 求职者id
     * @param k 聚类数
     * @param iterateTimes 迭代次数
     * @return 求职者所在聚类的所有人员的id
     */
    public List<Long> getSeekerCluster(Long seeker_id, int k, int iterateTimes){


//        QueryWrapper<Seeker> seekerQueryWrapper = new QueryWrapper<>();
//        //只获取设置了初始个人信息的求职者
//        seekerQueryWrapper.ne("name","");
//        List<Seeker> seekers = seekerService.list(seekerQueryWrapper);
        List<Seeker> seekers = seekerService.list();

        //求职者特征向量
        List<SeekerVector> seekerVectors = new LinkedList<>();
        LocalDate current = LocalDate.now();
        for(Seeker seeker : seekers) {
            QueryWrapper<JobIntention> jobIntentionQueryWrapper = new QueryWrapper<>();
            jobIntentionQueryWrapper.eq("seeker_id",seeker.getSeeker_id());
            JobIntention jobIntention = jobIntentionService.getOne(jobIntentionQueryWrapper);
            //如果没有求职意向，则不列入聚类范围
            if(jobIntention!=null) {
                SeekerVector seekerVector = new SeekerVector();
                Period period = Period.between(seeker.getBirth_date().toLocalDate(),current);
                seekerVector.setSeeker_id(seeker.getSeeker_id());
                seekerVector.setAge((double) period.getYears());
                seekerVector.setGender(seeker.getGender());
                seekerVector.setIdentity(seeker.getIdentity());
                seekerVector.setJob_address(jobIntention.getAddress_code());
                seekerVector.setJob_code(jobIntention.getJob_code());
                seekerVector.setSalary_min((double)jobIntention.getSalary_min());
                seekerVector.setSalary_max((double)jobIntention.getSalary_max());
                seekerVectors.add(seekerVector);
            }
        }
        List<HashMap<Long,SeekerVector>> clusters = new LinkedList<>();
        //聚类结果数组初始化
        for(int i=0;i<k;i++) {
            clusters.add(new HashMap<>());
        }
        //聚类原型
        List<SeekerVector> prototypes = new LinkedList<>();

        //随机下标集合
        HashSet<Integer> idxSet = new HashSet<>();
        Random random = new Random();
        //1.随机指定k个样本为聚类原型
        for(int i=0;i<k;i++) {
            int num = random.nextInt(0,seekerVectors.size());
            while(idxSet.contains(num)) {
                num = random.nextInt(0,seekerVectors.size());
            }
            idxSet.add(num);
            //设置聚类原型
            prototypes.add(seekerVectors.get(num));
        }

        //2.迭代聚类操作，直到满足迭代次数要求iterateTimes
        while(iterateTimes>0) {
            //3.聚类操作
            //按照混合相异性度量距离公式计算各个样本到各原型的距离
            //将其划分到离它最近的聚类原型所代表的聚类中

            //清空当前聚类划分，准备更新新的聚类内容
            clusters.forEach(HashMap::clear);

            for (SeekerVector seekerVector : seekerVectors) {
                double minDist = Double.MAX_VALUE;
                //划分聚类的下标
                int idx = 0;
                for (int j = 0; j < prototypes.size(); j++) {
                    double dist = getSeekerDist(seekerVector, prototypes.get(j));
                    if (dist < minDist) {
                        idx = j;
                        minDist = dist;
                    }
                }
                //添加到新聚类中
                HashMap<Long, SeekerVector> cluster = clusters.get(idx);
                cluster.put(seekerVector.getSeeker_id(), seekerVector);
            }

            //计算新的聚类原型
            updateSeekerClusterPrototypes(clusters,prototypes);

            iterateTimes--;
        }

        //4.对目标求职者所在聚类进行相似度排序
        HashMap<Long, SeekerVector> cluster = null;
        for (HashMap<Long, SeekerVector> c : clusters) {
            if(c.containsKey(seeker_id)) {
                cluster = c;
                break;
            }
        }
        List<Long> distList = new LinkedList<>();
        if(cluster!=null) {
            PriorityQueue<Pair<Long,Double>> q = new PriorityQueue<>(
                    (a,b) -> {
                        return a.getValue()-b.getValue()>0?1:-1;
                    }
            );
            SeekerVector seeker = cluster.get(seeker_id);
            cluster.remove(seeker_id);
            cluster.forEach((id,vector) -> {
                q.offer(new Pair<>(id,getSeekerDist(seeker,vector)));
            });
            while(!q.isEmpty()) {
                distList.add(q.poll().getKey());
            }
        }
        return distList;
    }

    /**
     * 更新聚类原型
     * @param clusters 当前聚类
     * @param prototypes 原型列表
     */
    private void updateSeekerClusterPrototypes(List<HashMap<Long,SeekerVector>> clusters, List<SeekerVector> prototypes) {
        for(int i=0;i<clusters.size();i++) {
            //对于离散编码采用众数
            int[] gender = new int[2];
            int[] identity = new int[2];
            int jobCodeMax = 0;
            String jobCode = null;
            HashMap<String,Integer> jobCodeMap = new HashMap<>();
            int jobAddressMax = 0;
            String jobAddress = null;
            HashMap<String,Integer> jobAddressMap = new HashMap<>();

            //对于连续数值采用均值
            double age_avg = 0;
            double salary_min_avg = 0;
            double salary_max_avg = 0;
            //遍历求和
            int cnt = clusters.get(i).values().size();
            for(SeekerVector seekerVector : clusters.get(i).values()) {
                gender[seekerVector.getGender()]++;
                identity[seekerVector.getIdentity()]++;
                //更新职位类别和地址的众数
                int jobCodeCnt = jobCodeMap.getOrDefault(seekerVector.getJob_code(),0)+1;
                if(jobCodeCnt>jobCodeMax) {
                    jobCodeMax = jobCodeCnt;
                    jobCode = seekerVector.getJob_code();
                }
                jobCodeMap.put(seekerVector.getJob_code(),jobCodeCnt);
                int jobAddressCnt = jobAddressMap.getOrDefault(seekerVector.getJob_address(),0)+1;
                if(jobAddressCnt>jobAddressMax) {
                    jobAddressMax = jobAddressCnt;
                    jobAddress = seekerVector.getJob_address();
                }
                jobAddressMap.put(seekerVector.getJob_address(), jobAddressCnt);
                age_avg += seekerVector.getAge();
                salary_min_avg += seekerVector.getSalary_min();
                salary_max_avg += seekerVector.getSalary_max();
            }
            age_avg /= cnt;
            salary_min_avg /= cnt;
            salary_max_avg /= cnt;
            //更新聚类原型
            SeekerVector prototype = prototypes.get(i);
            prototype.setGender(gender[0]>gender[1]?0:1);
            prototype.setIdentity(identity[0]>identity[1]?0:1);
            prototype.setAge(age_avg);
            prototype.setSalary_min(salary_min_avg);
            prototype.setSalary_max(salary_max_avg);
            prototype.setJob_code(jobCode);
            prototype.setJob_address(jobAddress);
        }
    }

    /**
     * 获取两个特征向量的距离
     * @param a 特征向量a
     * @param b 特征向量b
     * @return 两个特征向量的距离
     */
    private double getSeekerDist(SeekerVector a, SeekerVector b) {

        double ageDist = w_age*Math.abs(a.getAge() - b.getAge())/100;
        double minSalaryDist = w_salary_min*Math.abs(a.getSalary_min() - b.getSalary_min())/100;
        double maxSalaryDist = w_salary_max*Math.abs(a.getSalary_max() - b.getSalary_max())/100;
        double jobCodeDist = w_job_code*getJobCodeDist(a.getJob_code(),b.getJob_code());
        double jobAddressDist = w_job_address*getAddressCodeDist(a.getJob_address(),b.getJob_address());
        double genderDist = w_gender*getGenderDist(a.getGender(),b.getGender());
        double identityDist = w_identity*getIdentityDist(a.getIdentity(),b.getIdentity());

        double dist = ageDist + minSalaryDist + maxSalaryDist + jobCodeDist + jobAddressDist + genderDist + identityDist;
        return dist;
    }

    /**
     * 获取职位编码距离
     * @param a 职位编码a
     * @param b 职位编码b
     * @return 职位编码距离
     */
    private double getJobCodeDist(String a, String b) {
        //a的类别、具体职位编码
        String class_a = a.substring(0,2);
        String job_a = a.substring(2,4);
        //a的类别、具体职位编码
        String class_b = b.substring(0,2);
        String job_b = b.substring(2,4);
        double ret = 1;
        if(class_a.equals(class_b)) {
            if(job_a.equals(job_b)) {
                ret = 0;
            }
            else {
                ret = 0.5;
            }
        }
        return ret;
    }

    /**
     * 获取地址编码距离
     * @param a 地址编码a
     * @param b 地址编码b
     * @return 地址编码距离
     */
    private double getAddressCodeDist(String a, String b) {
        //a的省、市、区编码
        String p_a = a.substring(0,2);
        String c_a = a.substring(2,4);
        String d_a = a.substring(4,6);
        //b的省、市、区编码
        String p_b = b.substring(0,2);
        String c_b = b.substring(2,4);
        String d_b = b.substring(4,6);
        double ret = 1;
        if(p_a.equals(p_b)) {
            if(c_a.equals(c_b)) {
                if(d_a.equals(d_b)) {
                    //省市区均相同
                    ret = 0;
                }
                else {
                    //省市相同、区不同
                    ret = 0.33;
                }
            }
            else {
                //省相同、市区不相同
                ret = 0.66;
            }
        }
        return ret;
    }

    /**
     * 获取性别编码距离
     * @param a 性别编码a
     * @param b 性别编码b
     * @return 性别编码距离
     */
    private double getGenderDist(int a, int b) {
        if(a==b)
            return 0;
        else
            return 1;
    }

    /**
     * 获取身份编码距离
     * @param a 身份编码a
     * @param b 身份编码b
     * @return 身份编码距离
     */
    private double getIdentityDist(int a, int b) {
        if(a==b)
            return 0;
        else
            return 1;
    }

}
